# import pandas as pd
# import numpy as np
# from sklearn.cluster import KMeans
#
# def bin_indicators(df, bins=7, method='quantile'):
#     meta_cols = ['序号', '国名En', '国名Ch', '年份']
#     indicator_cols = [col for col in df.columns if col not in meta_cols]
#
#     df_binned = df[meta_cols].copy()
#     bin_meta = []
#
#     for col in indicator_cols:
#         series = df[col].dropna()
#
#         # 初始化分箱边界
#         bins_edges = None
#
#         if method == 'quantile':
#             # 等频分箱（按分位数）
#             try:
#                 bins_edges = np.unique(np.nanpercentile(series, np.linspace(0, 100, bins + 1)))
#             except Exception:
#                 bins_edges = np.linspace(series.min(), series.max(), bins + 1)
#
#         elif method == 'uniform':
#             # 等距分箱（每个箱间距相同）
#             bins_edges = np.linspace(series.min(), series.max(), bins + 1)
#
#         elif method == 'kmeans':
#             # 聚类分箱
#             reshaped = series.values.reshape(-1, 1)
#             km = KMeans(n_clusters=bins, n_init=10, random_state=42)
#             km.fit(reshaped)
#             centers = np.sort(km.cluster_centers_.flatten())
#             bins_edges = np.concatenate(([series.min() - 1e-6], (centers[:-1] + centers[1:]) / 2, [series.max() + 1e-6]))
#
#         elif method == 'std_dev':
#             # 按标准差划分（适用于近似正态分布）
#             mean = series.mean()
#             std = series.std()
#             bins_edges = [mean + std * (i - bins // 2) for i in range(bins + 1)]
#             bins_edges[0] = min(series.min(), bins_edges[0]) - 1e-6
#             bins_edges[-1] = max(series.max(), bins_edges[-1]) + 1e-6
#             bins_edges = sorted(bins_edges)
#
#         else:
#             raise ValueError("不支持的分箱方法，请选择 'quantile', 'uniform', 'kmeans', 'std_dev'")
#
#         bin_labels = list(range(1, len(bins_edges)))
#         df_binned[col] = pd.cut(df[col], bins=bins_edges, labels=bin_labels, include_lowest=True)
#
#         bin_meta.append({
#             'indicator': col,
#             'method': method,
#             'bins': bins,
#             'edges': bins_edges
#         })
#
#     return df_binned, pd.DataFrame(bin_meta)
"""
 更新：补充注释 & 函数说明
"""

import pandas as pd
import numpy as np
from sklearn.cluster import KMeans

def bin_indicators(df, bins=7, method='quantile'):
    """
    将指标数据进行离散分箱，并返回分箱后的数据和分箱元数据。

    参数：
        df (DataFrame): 输入数据，包含元数据列和指标列。
        bins (int): 分箱数目（默认为 7）。
        method (str): 分箱策略，可选值：
            - 'quantile': 等频分箱（按百分位将数据划分为频数相等的区间）；
            - 'uniform': 等距分箱（在[min, max]范围内将数据均匀切分成等宽区间）；
            - 'kmeans': KMeans 聚类分箱（将数据聚为 bins 类，再根据聚类中心确定边界）；
            - 'std_dev': 标准差分箱（以均值为中心，按标准差间距划分区间）。
    返回：
        df_binned (DataFrame): 分箱后的数据框，指标列的值替换为所属的箱号（类别标签，从1开始）。
        bin_meta (DataFrame): 分箱元数据，包括每个指标的名称、使用的分箱方法、bins 数以及对应的分箱边界列表。
    """
    meta_cols = ['序号', '国名En', '国名Ch', '年份']  # 元数据列名称
    indicator_cols = [col for col in df.columns if col not in meta_cols]  # 指标列名称列表

    # 复制元数据部分用于输出，并初始化分箱元信息列表
    df_binned = df[meta_cols].copy()
    bin_meta = []

    for col in indicator_cols:
        series = df[col].dropna()  # 当前指标列数据，忽略缺失值以计算分箱边界
        bins_edges = None  # 初始化分箱边界数组

        if method == 'quantile':
            # 等频分箱：按分位数将数据划分为 bins 个等频区间
            try:
                # np.linspace(0, 100, bins+1) 生成从0到100的等差序列，用于取得各分位点
                bins_edges = np.unique(np.nanpercentile(series, np.linspace(0, 100, bins + 1)))
            except Exception:
                # 若计算分位数出现错误（例如数据全部相等导致分位数无法区分），则退化为等距分箱
                bins_edges = np.linspace(series.min(), series.max(), bins + 1)

        elif method == 'uniform':
            # 等距分箱：将数值范围 [min, max] 均分为 bins 个等宽区间
            bins_edges = np.linspace(series.min(), series.max(), bins + 1)

        elif method == 'kmeans':
            # 聚类分箱：使用 KMeans 将数据聚为 bins 类，再根据聚类中心确定边界
            reshaped = series.values.reshape(-1, 1)
            km = KMeans(n_clusters=bins, n_init=10, random_state=42)
            km.fit(reshaped)
            centers = np.sort(km.cluster_centers_.flatten())
            # 分箱边界：相邻聚类中心中点作为边界，首尾加微小偏移以包含所有数据
            bins_edges = np.concatenate((
                [series.min() - 1e-6],
                (centers[:-1] + centers[1:]) / 2,
                [series.max() + 1e-6]
            ))

        elif method == 'std_dev':
            # 标准差分箱：基于均值和标准差，将数据以均值为中心按标准差间隔划分
            mean = series.mean()
            std = series.std()
            # 生成长度为 bins+1 的边界列表，每相邻边界间隔一个标准差
            bins_edges = [mean + std * (i - bins // 2) for i in range(bins + 1)]
            # 调整边界以确保覆盖数据的最小值和最大值，加入极小值偏移避免边界值遗漏
            bins_edges[0] = min(series.min(), bins_edges[0]) - 1e-6
            bins_edges[-1] = max(series.max(), bins_edges[-1]) + 1e-6
            bins_edges = sorted(bins_edges)

        else:
            # 输入 method 非支持选项，抛出错误
            raise ValueError("不支持的分箱方法，请选择 'quantile', 'uniform', 'kmeans' 或 'std_dev'")

        # 利用计算得到的边界对当前指标列进行分箱，标签为 1 ~ (len(bins_edges)-1)
        bin_labels = list(range(1, len(bins_edges)))
        df_binned[col] = pd.cut(df[col], bins=bins_edges, labels=bin_labels, include_lowest=True)
        # 保存该指标的分箱信息（名称、方法、箱数、边界）
        bin_meta.append({
            'indicator': col,
            'method': method,
            'bins': bins,
            'edges': bins_edges
        })

    return df_binned, pd.DataFrame(bin_meta)
